/** @file    Automation.cpp
 *  @author  Alessandro Polo
 *  @version $Id: Automation.cpp 2873 2010-08-08 19:06:41Z alex $
 *  @brief
 * File containing methods for the wosh::automations::Automation class.
 * The header for this class can be found in Automation.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <framework/automations/Automation.h>

 #include <core/Message.h>
 #include <core/BusManager.h>
 #include <core/SecurityManager.h>
 #include <core/DataModels.h>
 #include <core/Variant.h>
 #include <core/Functional.h>
 #include <core/Utilities.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/ObjectAllocator.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>


using namespace std;
namespace wosh {
 namespace automations {

 WOSH_REGISTER(wosh::automations::Automation, "wosh::Object", 1.0, _static_Automation )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

Automation::Automation( )
	: Object() {
	this->uid = Utilities::randomLong();
	this->uid = _GET_Abs(this->uid);

	Log.setIndent( 4 );
	Log.setLevel( LOG_INFO );
Log.setLevel( LOG_VERBOSE );
//Log.setLevel( LOG_DEBUG);

	this->mode_mapping = Automation::MAPPING_ANY_TRIGGER;
	this->enabled = true;
	this->priority = 0;

	clear();

	Log(LOG_DEBUG, " Initializing Properties.." );
	Properties.updateObjectKeys(this);
	Properties.set( _Automation_KEY_id,				this->uid,		Permission(Permission::Read) );
	Properties.set( _Automation_KEY_enabled,		true,			Permission::RW_RW_R__() );
	Properties.set( _Automation_KEY_priority,		this->priority,	Permission::RW_RW_R__() );
	Properties.set( _Automation_KEY_description,	"",				Permission::RW_RW_R__() );
	Properties.set( _Automation_KEY_LastExecutedTime, Variant_DT(0),Permission(Permission::Read) );
	Properties.set( _Automation_KEY_LastTrigger,	(int64)0,		Permission(Permission::Read) );
	Properties.set( _Automation_KEY_MonitoredBusses,"",				Permission::RW_RW_R__() );
	Properties.set( _Automation_KEY_ModeMapping,	(int)this->mode_mapping,		Permission::RW_RW_R__() );
 }

Automation::Automation( const Automation& m  )
	: Object(m) {
	Log.setIndent( 4 );
	Log.setLevel( LOG_INFO );
	clear();
	*this = m;
 }

Automation::~Automation() {
	Log(LOG_DEBUG, "~Automation : Freeing Triggers.." );
	this->triggers.free();
	Log(LOG_DEBUG, "~Automation : Freeing Effects.." );
	this->effects.free();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Automation::init() {
	Log(LOG_INFO, ":init() : initializing.." );

	Log(LOG_DEBUG, ":init() : Updating Properties.." );
	Properties.monitor(_KEY_name);
	Properties.monitor(_KEY_DebugLevel);
	Properties.monitor(_Automation_KEY_enabled);
	Properties.updateObjectKeys(this);
	Properties.setPermission( Object::getPermission(), Permission::Overwrite_All, true, true );
	Properties.setPropertiesProviderListener(this);

	Log(LOG_DEBUG, ":init() : Registering Methods.." );
	MethodPropertyT::createMethods( &Methods, &Properties, &Log );

	MethodChT::createMethods( &Methods, this, &Properties, &Log );

	MethodMessageResponse* mmExecute = Methods.create<MethodMessageResponse>( _Automation_METHOD_execute, "Execute", Permission(Permission::RX) );
	mmExecute->setMethod( this, (MethodMessageResponsePtr)&Automation::mmDoExecute );

	Methods.setPermission( Object::getPermission(), Permission::Overwrite_All, true, true );
	Methods.getListPermission().setMask( Permission::Read, Permission::Read, Permission::Read );

	Log(LOG_DEBUG, ":init() : Connecting Bus ("_Bus_Automation".." );
	BusAutomation.setMessageHandler(this);
	WRESULT bus_b_connected = BusAutomation.connect(_Bus_Automation, Bus::ACTION_DEFERRED);
	if ( WFAILED(bus_b_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "initBO", "FAILED#%d Connecting Bus ("_Bus_Automation")..", bus_b_connected );

	WRESULT ret = WRET_OK; WRESULT ret_t;
	Variant busses = Properties.getValueOf( _Automation_KEY_MonitoredBusses );
	if ( busses.isStringNotEmpty() ) {
		Log(LOG_DEBUG, ":init() : Connecting Bus(ses) %s", busses.asString().c_str() );
		vector<string> vBusses;
		Utilities::splitString(busses.asString(), ";", vBusses, false, true);
		for ( vector<string>::const_iterator it=vBusses.begin(); it!=vBusses.end(); ++it ) {
			ret_t = WoshKernel::getInstance()->busses().connectBus(this, *it, Bus::ACTION_DEFERRED);
			ret += ret_t;
			if ( WFAILED(ret_t) )
				Log(LOG_CRITICAL, ":init() : FAILED Connecting Bus '%s'!", (*it).c_str() );
		 }
	 }

	return ret;
 }

WRESULT Automation::deinit() {
	Log(LOG_INFO, ":deinit() : Deinitializing.." );
	Log(LOG_DEBUG, ":deinit() : Disconnecting Busses.." );
	WRESULT ret = WoshKernel::getInstance()->busses().disconnectBus(this, "*", Bus::ACTION_DEFERRED);
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS

void Automation::clear() {
	setName("Automation", this->uid);
	setEnabled(true);
	setLastExecutedTime(0);
	this->triggers.clear();
	this->effects.clear();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Automation::setID( int64 value ) {
	Log(LOG_INFO, ":setID(%"PRId64")", value );
	this->uid = value;
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_id, value );
	Properties.transactionEnd();
	Log.setContext( Utilities::format("Automation#%"PRId64, value) );
 }

void Automation::setName( const std::string& value, long id ) {
	if ( value == "Automation" )
		Object::setName(value, id);
	else
		Object::setName(value);
	Log.setContext(Object::getName());
	Object::getURI().setName(Object::getName());
	Properties.transactionBeginWrite();
	Properties.update( _KEY_name, Object::getName() );
	Properties.transactionEnd();
 }

void Automation::setEnabled( bool value ) {
	this->enabled = value;
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_enabled, value );
	Properties.transactionEnd();
 }

void Automation::setLastExecutedTime( int64 time ) {
	if ( time == -1 )
		this->lastExecutedTime = Utilities::std_time();
	else
		this->lastExecutedTime = time;
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_LastExecutedTime, Variant(this->lastExecutedTime, Variant::DATETIME) );
	Properties.transactionEnd();
 }

void Automation::setLastTrigger( const Message* trigger_message ) {
	if ( trigger_message == NULL )
		this->lastTrigger = 0;
	else
		this->lastTrigger = trigger_message->getID();
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_LastTrigger, this->lastTrigger );
	Properties.transactionEnd();
 }

void Automation::setPriority( int value ) {
	this->priority = value;
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_priority, value );
	Properties.transactionEnd();
 }

void Automation::setModeMapping( MODE_EFFECT_MAPPING value ) {
	this->mode_mapping = value;
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_ModeMapping, (int)value );
	Properties.transactionEnd();
 }

void Automation::setDescription( const std::string& value ) {
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_description, value );
	Properties.transactionEnd();
 }

void Automation::setMonitoredBusses( const std::vector<std::string>& busses ) {
	Properties.transactionBeginWrite();
	Properties.update( _Automation_KEY_MonitoredBusses, Utilities::joinVector(busses, "; ") );
	Properties.transactionEnd();
 }

void Automation::getMonitoredBusses( std::vector<std::string>& busses ) const {
	Utilities::splitString( this->Properties.getValueOf(_Automation_KEY_MonitoredBusses).toString(), ";", busses, false, true );
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// TRIGGERS AND EFFECTS

void Automation::evaluateMessageTrigger( const Message& message ) {
	if ( Log.isDebug() )
		Log(LOG_DEBUG, ":evaluateMessageTrigger(%"PRId64") from %s [bus:%s]", message.getID(), message.getSource().toString().c_str(), message.getCurrentBus().c_str() );
	int retValue; int index = 0; int cmp_res;
	this->triggers.transactionBeginRead();
	this->effects.transactionBeginRead();
	tMessageFilterIntMap::ConstIterator it;
	const tMessageFilterIntMap::ConstIterator it_end = this->triggers.end();
	for ( it=this->triggers.begin(); it!=it_end; ++it, ++index ) {
		if ( it->first == NULL ) continue;
	//	if ( it->second == WOSH_COMPARE_MASK_ANY ) continue; // ignoring such filter
		retValue = it->first->validateMessageHeader(message);
		if ( retValue != it->second ) {
			Log(LOG_DEBUG, ":evaluateMessageTrigger(%"PRId64") : Ignoring by Header (%d[%s]!=%d) for Trigger#%d", message.getID(), retValue, MessageFilter::getMessageHeaderFilterResultsAsString(retValue).c_str(), it->second, index );
			continue;
		 }
		Log(LOG_VERBOSE, ":evaluateMessageTrigger(%"PRId64") : Validated[%d] Header of Trigger#%d", message.getID(), retValue, index );
		retValue = it->first->validateMessageContent(message, &cmp_res, MessageFilter::CONTENT_OPT_EMPTYPATTERN);
		if ( (retValue & MessageFilter::CONTENT_CMP_VALID) != MessageFilter::CONTENT_CMP_VALID ) {
			Log(LOG_DEBUG, ":evaluateMessageTrigger(%"PRId64") : Ignoring by Content(%d[%s], CmpRes=%d) for Trigger#%d", message.getID(), retValue, MessageFilter::getMessageContentFilterResultsAsString(retValue).c_str(), cmp_res, index );
			continue;
		 }
		Log(LOG_VERBOSE, ":evaluateMessageTrigger(%"PRId64") : Validated Trigger#%d", message.getID(), index, retValue );
		execute_(&message, it->first, index);
	 }
	this->effects.transactionEnd();
	this->triggers.transactionEnd();
 }

Message* Automation::generateEffect( const Message* effect_template, const Message* trigger_message, const MessageFilter* ) const {
	if ( effect_template == NULL ) return NULL;
	Message* effect = effect_template->clone(); 
	updateVariables(effect, trigger_message);
	signMessage(effect);
	return effect;
 }

WRESULT Automation::execute( const Message* trigger_message, const MessageFilter* trigger_filter ) {
	MutexLockerRead(this->effects.getMutex());
	return execute_(trigger_message, trigger_filter, 0);
 }

WRESULT Automation::execute_( const Message* trigger_message, const MessageFilter* trigger_filter, int trigger_index ) {
	WRESULT ret = WRET_OK;
	if ( this->mode_mapping == Automation::MAPPING_INDEX_TRIGGER ) {
		if ( trigger_index < this->effects.size() ) {
			const Message* effect_template = this->effects.itemAt(trigger_index);
			Message* effectMsg = generateEffect(effect_template, trigger_message, trigger_filter);
			Log(LOG_INFO, ":execute() : Trigger[%d] => Effect..", trigger_index );
			postMessage(effectMsg);
			//ret = WoshKernel::getInstance()->busses().postMessage( effectMsg, effectMsg->getDestinationBus() );
		 }
		else
			Log(LOG_CRITICAL, ":execute() : Trigger[%d/%d] OutOfBounds!", trigger_index, this->effects.size() );
	 }
	else {
		int csent = 0;
		tMessageList::ConstIterator it;
		const tMessageList::ConstIterator it_end = this->effects.end();
		for ( it=this->effects.begin(); it!=it_end; ++it ) {
			if ( *it == NULL ) continue;
			Message* effectMsg = generateEffect(*it, trigger_message, trigger_filter);
			if ( effectMsg == NULL ) {
				Log(LOG_WARNING, ":execute() : NULL Effect!" );
				continue;
			 }
			//ret += WoshKernel::getInstance()->busses().postMessage( effectMsg, effectMsg->getDestinationBus() );
			postMessage(effectMsg);
			++csent;
		 }
		if ( csent == 0 )
			Log(LOG_WARNING, ":execute() : No Effects sent.." );
		else
			Log(LOG_INFO, ":execute() : %d Effect(s) sent..", csent );
	 }
	setLastExecutedTime();
	Message* msgEvent = new Message( new Automation_Executed(this, trigger_message));
	msgEvent->setDestinationBroadcast(true);
	msgEvent->setDestinationBus(_Bus_Automation);
	signMessage(msgEvent);
	BusAutomation.postMessage(msgEvent);
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////// TRIGGERS AND EFFECTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// HELPERS

SecurityToken* Automation::signMessage( Message* message, const SecurityToken* source_token ) const {
	if ( message == NULL ) return NULL;
	message->setSource(this); // 
	if ( source_token != NULL ) // we are forwarding a message, it's important to keep track of source security token
		return SecurityManager::getCredentialProxy().signMessage(message, source_token, this); // source token will be encapsulated
	return SecurityManager::getCredentialImplicit().signMessage(message, this);
 }

void Automation::postMessage( Message* message, const SecurityToken* source_token ) {
	signMessage(message, source_token);
	BusAutomation.postMessage(message);
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// HELPERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void Automation::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( message.getSourceUUID() == getEntityID() ) return; // avoid any loopback
	// evaluate control messages (as set/get Properties, exec methods, ..)
	if ( message.getContent()->isRequest() ) {
		if ( MessageFilter::isMessageTarget(message, this) ) {
			Message* reply = MPC_busMessageRequest( message, this->Methods, this->Log );
			if ( reply != NULL ) {
				if ( !reply->hasSecurityToken() ) signMessage(reply);
				if ( source != NULL )
					source->postMessage(reply);
				else
					BusAutomation.postMessage(reply);
			 }
		 }
	 }
	if ( !isEnabled() )
		return;
	// evaluate if message is a trigger
	evaluateMessageTrigger(message);
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool Automation::updatingProperty( bool& , const Variant& value_proposed, Property& property_current, const PropertiesProvider* ) {
	if ( property_current.getKey() == _KEY_name ) { // if we are here, we allow to edit name
		Log(LOG_INFO, " Changing Automation Name, Log-Context will change to '%s'", value_proposed.toString().c_str() );
		Object::setName(value_proposed.toString());
		Log.setContext(value_proposed.toString());
		return true;
	 }
	else if ( property_current.getKey() == _KEY_DebugLevel )
		return MPC_PropertyParse_LogLevel( value_proposed, this->Log );

	else if ( property_current.getKey() == _Automation_KEY_enabled ) {
		this->enabled = value_proposed.toBoolean(true);
		return true;
	 }
	// not handled, sound strange but probably its ok since we are the lower (base) implementation
	return true;
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////// OPERATORS

Automation& Automation::operator=(const Automation& m){
	if (this == &m) return *this; // same object?
	(Object)*this = (const Object)m;
	this->uid = m.uid;
	this->enabled = m.enabled;
	this->priority = m.priority;
	this->lastExecutedTime = m.lastExecutedTime;
	this->lastTrigger = m.lastTrigger;
	this->Properties = m.Properties;

	return *this;
	this->triggers.transactionBeginWrite();
	m.triggers.transactionBeginRead();
	this->triggers.free();
	tMessageFilterIntMap::ConstIterator it;
	const tMessageFilterIntMap::ConstIterator it_end = m.triggers.end();
	for( it=m.triggers.begin(); it!=it_end; ++it ) {
		if ( it->first == NULL )
			this->triggers.set(NULL, it->second);
		else
			this->triggers.set(it->first->clone(), it->second);
	 }
	m.triggers.transactionEnd();
	this->triggers.transactionEnd();

	this->effects.transactionBeginWrite();
	m.effects.transactionBeginRead();
	this->effects.free();
	tMessageList::ConstIterator it_e;
	const tMessageList::ConstIterator it_e_end = m.effects.end();
	for( it_e=m.effects.begin(); it_e!=it_e_end; ++it_e ) {
		if ( *it_e == NULL )
			this->effects.push_back(NULL);
		else
			this->effects.push_back( (*it_e)->clone() );
	 }
	m.effects.transactionEnd();
	this->effects.transactionEnd();

	return *this;
 }

bool Automation::operator==(const Automation &other) const {
	if ( this->uid != other.uid ) return false;
	if ( this->enabled != other.enabled ) return false;
	if ( this->Properties != other.Properties ) return false;
	return true;
 }

/////////////////////////////////////////////////////////////////////////////////////////////////// OPERATORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////// SERIALIZATION

std::string Automation::toString() const {
	return Object::getName();
 }

bool Automation::updateHash( DataHashContext* hash_context ) const {
	if ( hash_context == NULL ) return false;
	bool ret = Object::updateHash(hash_context);
	ret = hash_context->update( this->uid ) & ret;
	ret = hash_context->update( this->enabled ) & ret;
	ret = hash_context->update( this->priority ) & ret;
	ret = hash_context->update( this->lastExecutedTime ) & ret;
	ret = hash_context->update( this->enabled ) & ret;

	tMessageList::ConstIterator it_ef;
	const tMessageList::ConstIterator it_ef_end = this->effects.end();
	for ( it_ef=this->effects.begin(); it_ef!=it_ef_end; ++it_ef ) {
		if ( *it_ef == NULL ) continue;
		ret = (*it_ef)->updateHash(hash_context) & ret;
	 }

	ret = this->Properties.updateHash(hash_context) & ret;
	return ret;
 }

/////////////////////////////////////////////////////////////////////////////////////////////// SERIALIZATION
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Automation::updateVariables( Message* message, const Message* trigger_message ) const {
	// update 'static' vars
	Properties.transactionBeginRead();
	PropertiesProvider::ConstIterator it;
	const PropertiesProvider::ConstIterator it_end = Properties.getMap().end();
	for( it=Properties.getMap().begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		if ( it->first.find("VAR_") != 0 ) continue;
		MutexLockerRead(&it->second->mux);
		if ( it->second->data == NULL ) continue;
		replaceVariable(message, trigger_message, it->first.substr(4), it->second->data->getValue() );
	 }
	Properties.transactionEnd();
 }

WRESULT Automation::replaceVariable( Message* message, const Message* trigger_message, const std::string& key, const Variant& value ) const {
	if ( message == NULL ) return WRET_ERR_PARAM;
//	message->replaceField(key, &value);
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////// HELPERS/CONVERTERS

DataFieldCollector<Automation>* Automation::getDataFields() {
	DataFieldCollector<Automation>* fields = new DataFieldCollector<Automation>();
	fields->add<int64, Variant>(&Automation::getID, "ID", 0);
	fields->add<const std::string&, Variant>(&Automation::getName, "Name", 1);
	fields->add<const char*,Variant>(&Automation::getClassName, "Type", 2);
	fields->add<bool,Variant>(&Automation::isEnabled, "Enabled", 3);
	fields->add<int,Variant>(&Automation::getPriority, "Priority", 4);
	fields->add<int64,Variant_DT>(&Automation::getLastExecutedTime, "Last TS", 5);
	return fields;
 }

////////////////////////////////////////////////////////////////////////////////////////// HELPERS/CONVERTERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* Automation::mmDoExecute( const Message* messageRequest ) {
	if ( messageRequest == NULL || messageRequest->isEmpty() ) return NULL;
	const Request* request = messageRequest->getContent()->asRequest();
	if ( request == NULL ) return NULL;
	Log(LOG_VERBOSE, ":mmDoExecute()" );
	WRESULT ret = execute(messageRequest, NULL);
	return request->replyResponse(ret);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::automations::Automation_Executed, "wosh::Fact", 1.0, _static_Automation_Executed )

Automation_Executed::Automation_Executed( const wosh::automations::Automation* automation, const Message* trigger )
	: wosh::Fact( _Automation_EVENT_Executed ) {
	List* list = new List(2);
	if ( automation != NULL )
		list->push_back<Variant>(automation->getID());
	else
		list->push_back( new Variant() );
	if ( trigger != NULL )
		list->push_back<Variant>(trigger->getID());
	else
		list->push_back( new Variant() );
	setData(list);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace automations
}; // namespace wosh
